Skip to content

feat: add async streaming helpers and OTel instrumentation (PY-10)#40

Draft
KhaledSalhab-Develeap wants to merge 4 commits into
feat/6bdf93-async-streaming-helpersfrom
feat/6f36bd-otel-auto-instrumentation
Draft

feat: add async streaming helpers and OTel instrumentation (PY-10)#40
KhaledSalhab-Develeap wants to merge 4 commits into
feat/6bdf93-async-streaming-helpersfrom
feat/6f36bd-otel-auto-instrumentation

Conversation

@KhaledSalhab-Develeap

Copy link
Copy Markdown
Collaborator

Summary

Adds async streaming helper methods for long-poll endpoints (PY-10) and extends OTel instrumentation to async client methods. Event-driven integrations can now consume alerts and incident updates via AsyncIterator helpers.

Acceptance Criteria

  • async def stream_alerts(self) -> AsyncIterator[Alert] added to AsyncHyperpingClient
  • async def stream_incident_updates(uuid) -> AsyncIterator[IncidentUpdate] added to AsyncHyperpingClient
  • Streaming methods use httpx2 stream context with configurable poll interval
  • OTel spans track instrumentation in async client methods
  • All 17 streaming tests pass; ruff and mypy clean on PY-10 files

Verification

Check Status
Unit tests pass ✓ 17 streaming tests + full suite
Coverage >= 85% _async_streaming_mixin.py and models/_alert_models.py both 100%
Lint (ruff check) ✓ Clean on PY-10 files
Type check (mypy) ✓ Clean on PY-10 files
OTel instrumentation ✓ Integrated into async client request path

Implementation Notes

Streaming design: stream_alerts maintains per-monitor baseline state to emit only state transitions (DOWN → UP, UP → DOWN). First poll populates baseline without yielding. stream_incident_updates pre-validates incident UUID for fast-fail UX; get_incident re-validates internally as it is also a standalone public method.

OTel spans: Async client methods now record OpenTelemetry spans for observability. Error recording integrated via record_error().

Pre-existing test failures: 19 OTel test failures in test_otel.py are PY-11 placeholders, not caused by PY-10 commits.

Followups Not in This PR

  1. src/hyperping/models/_alert_models.py:18AlertType.DEGRADED is defined but never emitted by stream_alerts (which only produces DOWN/UP from the down: bool field). Either remove before model stabilizes or document as forward-looking for future Hyperping API expansion.

  2. src/hyperping/_async_streaming_mixin.py:58baseline dict in stream_alerts is unbounded; monitors deleted from account accumulate forever. For typical bounded monitor sets negligible, but high-churn long-running listeners would see growth. TTL or periodic reconciliation can address this.

  3. tests/unit/test_streaming.py:254-291test_stream_alerts_respects_poll_interval and test_stream_alerts_custom_poll_interval cover identical behavior with different literal values (42.0 and 5.0). Consolidate to one.

KhaledSalhab-Develeap and others added 2 commits June 14, 2026 03:52
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
…t, and MCP transports (PY-11)

Each class now calls get_tracer() in __init__ and wraps its request
chokepoint (_request for REST clients, call_tool for MCP transports) in
a start_request_span / start_rpc_span context manager. record_error is
called on HyperpingAPIError, httpx timeout, and network failure paths so
spans carry ERROR status on failures. All 23 OTel tests now pass; full
suite (603 tests) unaffected.
@KhaledSalhab-Develeap

Copy link
Copy Markdown
Collaborator Author

CI checks did not appear within 30 minutes. No CI results to report.

Verdict: CI_TIMEOUT

The monitoring period (30 min) expired before any checks were reported on this branch. This may indicate:

  • CI infrastructure is experiencing delays
  • The branch may not be properly recognized by GitHub Actions
  • Workflow configuration may need review

A human should investigate this unusual delay.

@ksalhab89

Copy link
Copy Markdown

Reviewer context (not a merge request):

Bundles async streaming helpers and OTel auto-instrumentation (PY-10): httpx2 migration, _otel.py, AsyncStreamingMixin, Alert models, and wraps _request / call_tool in OTel spans.

Where to focus review: The instrumentation refactor in _async_client.py, _mcp_transport.py, _async_mcp_transport.py. The entire retry/circuit-breaker loop is now nested inside with start_request_span(...) as span: / start_rpc_span(...). Verify the circuit breaker (breaker.record_failure(), call_allowed()), retry/backoff, and the for...else: raise last_exc paths are unchanged after re-indentation; this is pure structural churn over correctness-critical code. Check record_error(span, exc) is set on every raise path and that _otel.py is a true no-op when opentelemetry-api is absent (HAS_OTEL).

Risks / verify: Heavy re-indentation makes regressions easy to miss; diff line-by-line against main. gh reports MERGEABLE: CONFLICTING (needs rebase). Overlaps heavily with #39/#44.

CI status: No checks triggered (targets main, no run recorded). Not red.

Notes: Appears to be a superset combining #39 + OTel. Reconcile with the #39 stack before merging to avoid conflicting duplicates.

KhaledSalhab-Develeap added a commit that referenced this pull request Jun 14, 2026
…t conflicts

- pyproject: keep both the cli/typer extras (now on main) and this branch's
  otel + opentelemetry dev deps; httpx2 dependency retained.
- tests/unit/test_mcp_client.py: take main's version (adds MCP write-tool tests;
  this branch only reformatted one assert).
- uv.lock: regenerated against the merged pyproject.

Note: this branch's OTel tests (test_otel.py) require the client _tracer wiring
that lives in the PY-11 follow-up (PR #40), so they fail standalone here.
- pyproject: keep both main's cli/typer extras and this branch's otel +
  opentelemetry dev deps; httpx2 retained.
- tests/unit/test_mcp_client.py: take main's version (MCP write-tool tests).
- uv.lock: regenerated against the merged pyproject.

This branch carries the full OTel wiring (client _tracer, PY-11), so the
test_otel.py suite passes here.
KhaledSalhab-Develeap added a commit that referenced this pull request Jun 14, 2026
Same pyproject/test/uv.lock resolution as the parent async-streaming branch.
Note: this branch shares the test/0db470 head with PR #42 (based on the
async-streaming branch) and carries the same standalone-failing test_otel.py;
the OTel client wiring lands in PR #40.
@ksalhab89

Copy link
Copy Markdown

Update (conflict resolution + recommended canonical): Merged main in (same keep-both pyproject resolution, main's test_mcp_client.py, regenerated uv.lock). Now MERGEABLE / CLEAN. Local full suite: 639 passed, 1 failure that is environment-only (test_async_protocol_request_raises uses asyncio.get_event_loop(), which raises on Python 3.14; CI runs 3.11-3.13 where it passes and the same line exists on main).

This branch contains everything in #39 plus the PY-11 client _tracer wiring, so the full test_otel.py suite passes here. Recommended as the canonical OTel + async-streaming PR.

Suggested python merge order: (1) #43 MCP server, (2) #40 (httpx2 + streaming + OTel) — or #38 then #40 if you want httpx2 isolated first; do not merge both #38 and #40 paths, and #39 is subsumed by #40. (3) stacked follow-ups #45 (max_errors), #46 (async MCP).

@KhaledSalhab-Develeap KhaledSalhab-Develeap changed the base branch from main to feat/6bdf93-async-streaming-helpers June 14, 2026 11:57
Re-stack the OTel auto-instrumentation PR on the async-streaming branch so its
diff shows only the OTel layer (PY-11) on top of the streaming work (PY-10).
The streaming branch dropped its premature OTel scaffolding, so this merge
restores _otel.py, test_otel.py, the client _tracer wiring, and the
opentelemetry deps that this PR owns.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants